View Javadoc
1 /* 2 * $Header: /home/cvs/jakarta-commons/betwixt/src/java/org/apache/commons/betwixt/io/BeanWriter.java,v 1.9 2002/08/01 03:58:01 jstrachan Exp $ 3 * $Revision: 1.9 $ 4 * $Date: 2002/08/01 03:58:01 $ 5 * 6 * ==================================================================== 7 * 8 * The Apache Software License, Version 1.1 9 * 10 * Copyright (c) 1999-2002 The Apache Software Foundation. All rights 11 * reserved. 12 * 13 * Redistribution and use in source and binary forms, with or without 14 * modification, are permitted provided that the following conditions 15 * are met: 16 * 17 * 1. Redistributions of source code must retain the above copyright 18 * notice, this list of conditions and the following disclaimer. 19 * 20 * 2. Redistributions in binary form must reproduce the above copyright 21 * notice, this list of conditions and the following disclaimer in 22 * the documentation and/or other materials provided with the 23 * distribution. 24 * 25 * 3. The end-user documentation included with the redistribution, if 26 * any, must include the following acknowlegement: 27 * "This product includes software developed by the 28 * Apache Software Foundation (http://www.apache.org/)." 29 * Alternately, this acknowlegement may appear in the software itself, 30 * if and wherever such third-party acknowlegements normally appear. 31 * 32 * 4. The names "The Jakarta Project", "Commons", and "Apache Software 33 * Foundation" must not be used to endorse or promote products derived 34 * from this software without prior written permission. For written 35 * permission, please contact apache@apache.org. 36 * 37 * 5. Products derived from this software may not be called "Apache" 38 * nor may "Apache" appear in their names without prior written 39 * permission of the Apache Group. 40 * 41 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED 42 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 43 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 44 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR 45 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 46 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 47 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 48 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 49 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 50 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 51 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 52 * SUCH DAMAGE. 53 * ==================================================================== 54 * 55 * This software consists of voluntary contributions made by many 56 * individuals on behalf of the Apache Software Foundation. For more 57 * information on the Apache Software Foundation, please see 58 * <http://www.apache.org/>;. 59 * 60 * $Id: BeanWriter.java,v 1.9 2002/08/01 03:58:01 jstrachan Exp $ 61 */ 62 package org.apache.commons.betwixt.io; 63 64 import java.beans.IntrospectionException; 65 import java.io.BufferedWriter; 66 import java.io.IOException; 67 import java.io.OutputStream; 68 import java.io.OutputStreamWriter; 69 import java.io.Writer; 70 import java.util.Iterator; 71 import java.util.HashMap; 72 73 import org.apache.commons.logging.Log; 74 import org.apache.commons.logging.LogFactory; 75 76 import org.apache.commons.betwixt.AttributeDescriptor; 77 import org.apache.commons.betwixt.ElementDescriptor; 78 import org.apache.commons.betwixt.XMLBeanInfo; 79 import org.apache.commons.betwixt.XMLIntrospector; 80 import org.apache.commons.betwixt.expression.Context; 81 import org.apache.commons.betwixt.expression.Expression; 82 import org.apache.commons.betwixt.io.id.SequentialIDGenerator; 83 84 import org.xml.sax.SAXException; 85 86 /*** <p><code>BeanWriter</code> outputs beans as XML to an io stream.</p> 87 * 88 * <p>The output for each bean is an xml fragment 89 * (rather than a well-formed xml-document). 90 * This allows bean representations to be appended to a document 91 * by writing each in turn to the stream. 92 * So to create a well formed xml document, 93 * you'll need to write the prolog to the stream first. 94 * If you append more than one bean to the stream, 95 * then you'll need to add a wrapping root element as well. 96 * 97 * <p> The line ending to be used is set by {@link #setEndOfLine}. 98 * 99 * <p> The output can be formatted (with whitespace) for easy reading 100 * by calling {@link #enablePrettyPrint}. 101 * The output will be indented. 102 * The indent string used is set by {@link #setIndent}. 103 * 104 * <p> Bean graphs can sometimes contain cycles. 105 * Care must be taken when serializing cyclic bean graphs 106 * since this can lead to infinite recursion. 107 * The approach taken by <code>BeanWriter</code> is to automatically 108 * assign an <code>ID</code> attribute value to beans. 109 * When a cycle is encountered, 110 * an element is written that has the <code>IDREF</code> attribute set to the 111 * id assigned earlier. 112 * 113 * <p> The names of the <code>ID</code> and <code>IDREF</code> attributes used 114 * can be customized by the <code>XMLBeanInfo</code>. 115 * The id's used can also be customized by the user 116 * via <code>IDGenerator</code> subclasses. 117 * The implementation used can be set by the <code>IdGenerator</code> property. 118 * BeanWriter defaults to using <code>SequentialIDGenerator</code> 119 * which supplies id values in numeric sequence. 120 * 121 * <p>If generated <code>ID</code> attribute values are not acceptable in the output, 122 * then this can be disabled by setting the <code>WriteIDs</code> property to false. 123 * If a cyclic reference is encountered in this case then a 124 * <code>CyclicReferenceException</code> will be thrown. 125 * When the <code>WriteIDs</code> property is set to false, 126 * it is recommended that this exception is caught by the caller. 127 * 128 * 129 * @author <a href="mailto:jstrachan@apache.org">James Strachan</a> 130 * @author <a href="mailto:martin@mvdb.net">Martin van den Bemt</a> 131 * @version $Revision: 1.9 $ 132 */ 133 public class BeanWriter extends AbstractBeanWriter { 134 135 /*** Escaped <code><</code> entity */ 136 private final static String LESS_THAN_ENTITY = "<"; 137 /*** Escaped <code>></code> entity */ 138 private final static String GREATER_THAN_ENTITY = ">"; 139 /*** Escaped <code>&</code> entity */ 140 private final static String AMPERSAND_ENTITY = "&"; 141 /*** Escaped <code>'</code> entity */ 142 private final static String APOSTROPHE_ENTITY = "'"; 143 /*** Escaped <code>"</code> entity */ 144 private final static String QUOTE_ENTITY = """; 145 146 /*** Where the output goes */ 147 private Writer writer; 148 /*** text used for end of lines. Defaults to <code>\n</code>*/ 149 private static final String EOL = "\n"; 150 /*** text used for end of lines. Defaults to <code>\n</code>*/ 151 private String endOfLine = EOL; 152 /*** indentation text */ 153 private String indent; 154 155 /*** should we flush after writing bean */ 156 private boolean autoFlush; 157 /*** Log used for logging (Doh!) */ 158 private Log log = LogFactory.getLog( BeanWriter.class ); 159 160 /*** 161 * <p> Constructor uses <code>System.out</code> for output.</p> 162 */ 163 public BeanWriter() { 164 this( System.out ); 165 } 166 167 /*** 168 * <p> Constuctor uses given <code>OutputStream</code> for output.</p> 169 * 170 * @param out write out representations to this stream 171 */ 172 public BeanWriter(OutputStream out) { 173 this.writer = new BufferedWriter( new OutputStreamWriter( out ) ); 174 this.autoFlush = true; 175 } 176 177 /*** 178 * <p> Constructor sets writer used for output.</p> 179 * 180 * @param writer write out representations to this writer 181 */ 182 public BeanWriter(Writer writer) { 183 this.writer = writer; 184 } 185 186 /*** 187 * A helper method that allows you to write the XML Declaration. 188 * This should only be called once before you output any beans. 189 * 190 * @param xmlDeclaration is the XML declaration string typically of 191 * the form "<xml version='1.0' encoding='UTF-8' ?> 192 */ 193 public void writeXmlDeclaration(String xmlDeclaration) throws IOException { 194 writer.write( xmlDeclaration ); 195 writePrintln(); 196 } 197 198 /*** 199 * Allows output to be flushed on the underlying output stream 200 */ 201 public void flush() throws IOException { 202 writer.flush(); 203 } 204 205 /*** 206 * Closes the underlying output stream 207 */ 208 public void close() throws IOException { 209 writer.close(); 210 } 211 212 public void write(Object bean) throws IOException, SAXException, IntrospectionException { 213 214 super.write(bean); 215 216 if ( autoFlush ) { 217 writer.flush(); 218 } 219 } 220 221 222 /*** 223 * <p> Switch on formatted output. 224 * This sets the end of line and the indent. 225 * The default is adding 2 spaces and a newline 226 */ 227 public void enablePrettyPrint() { 228 endOfLine = EOL; 229 indent = " "; 230 } 231 232 /*** Returns the string used for end of lines */ 233 public String getEndOfLine() { 234 return endOfLine; 235 } 236 237 /*** 238 * Sets the string used for end of lines 239 * Produces a warning the specified value contains an invalid whitespace character 240 */ 241 public void setEndOfLine(String endOfLine) { 242 this.endOfLine = endOfLine; 243 for (int i = 0; i < endOfLine.length(); i++) { 244 if (!Character.isWhitespace(endOfLine.charAt(i))) { 245 log.warn("Invalid EndOfLine character(s)"); 246 break; 247 } 248 } 249 250 } 251 252 /*** Returns the string used for indentation */ 253 public String getIndent() { 254 return indent; 255 } 256 257 /*** Sets the string used for end of lines */ 258 public void setIndent(String indent) { 259 this.indent = indent; 260 } 261 262 /*** 263 * <p> Get the current level for logging. </p> 264 * 265 * @return a <code>org.apache.commons.logging.Log</code> level constant 266 */ 267 public Log getLog() { 268 return log; 269 } 270 271 /*** 272 * <p> Set the current logging level. </p> 273 * 274 * @param level a <code>org.apache.commons.logging.Log</code> level constant 275 */ 276 public void setLog(Log log) { 277 this.log = log; 278 } 279 280 281 // Expression methods 282 //------------------------------------------------------------------------- 283 284 /*** Express an element tag start using given qualified name */ 285 protected void expressElementStart(String qualifiedName) throws IOException { 286 if ( qualifiedName == null ) { 287 // XXX this indicates a programming error 288 log.fatal( "[expressElementStart]Qualified name is null." ); 289 throw new RuntimeException( "Qualified name is null." ); 290 } 291 292 writePrintln(); 293 writeIndent(); 294 writer.write( '<' ); 295 writer.write( qualifiedName ); 296 } 297 298 protected void expressTagClose() throws IOException { 299 writer.write( '>' ); 300 } 301 302 /*** Express an element end tag using given qualifiedName */ 303 protected void expressElementEnd(String qualifiedName) throws IOException { 304 if (qualifiedName == null) { 305 // XXX this indicates a programming error 306 log.fatal( "[expressElementEnd]Qualified name is null." ); 307 throw new RuntimeException( "Qualified name is null." ); 308 } 309 310 writer.write( "</" ); 311 writer.write( qualifiedName ); 312 writer.write( '>' ); 313 } 314 315 /*** Express an empty element end */ 316 protected void expressElementEnd() throws IOException { 317 writer.write( "/>" ); 318 } 319 320 /*** Express body text */ 321 protected void expressBodyText(String text) throws IOException { 322 if ( text == null ) { 323 // XXX This is probably a programming error 324 log.error( "[expressBodyText]Body text is null" ); 325 326 } else { 327 writer.write( escapeBodyValue(text) ); 328 } 329 } 330 331 /*** Express an attribute */ 332 protected void expressAttribute( 333 String qualifiedName, 334 String value) 335 throws 336 IOException{ 337 if ( value == null ) { 338 // XXX probably a programming error 339 log.error( "Null attribute value." ); 340 return; 341 } 342 343 if ( qualifiedName == null ) { 344 // XXX probably a programming error 345 log.error( "Null attribute value." ); 346 return; 347 } 348 349 writer.write( ' ' ); 350 writer.write( qualifiedName ); 351 writer.write( "=\"" ); 352 writer.write( escapeAttributeValue(value) ); 353 writer.write( '\"' ); 354 } 355 356 357 // Implementation methods 358 //------------------------------------------------------------------------- 359 360 /*** Writes out an empty line. 361 * Uses current <code>endOfLine</code>. 362 */ 363 protected void writePrintln() throws IOException { 364 if ( endOfLine != null ) { 365 writer.write( endOfLine ); 366 } 367 } 368 369 /*** Writes out <code>indent</code>'s to the current <code>indentLevel</code> 370 */ 371 protected void writeIndent() throws IOException { 372 if ( indent != null ) { 373 for ( int i = 0; i < indentLevel; i++ ) { 374 writer.write( getIndent() ); 375 } 376 } 377 } 378 379 /*** 380 * <p>Escape the <code>toString</code> of the given object. 381 * For use as body text.</p> 382 */ 383 protected String escapeBodyValue(Object value) { 384 StringBuffer buffer = new StringBuffer(value.toString()); 385 for (int i=0, size = buffer.length(); i <size; i++) { 386 switch (buffer.charAt(i)) { 387 case '<': 388 buffer.replace(i, i+1, LESS_THAN_ENTITY); 389 size += 3; 390 i+=3; 391 break; 392 case '>': 393 buffer.replace(i, i+1, GREATER_THAN_ENTITY); 394 size += 3; 395 i += 3; 396 break; 397 case '&': 398 buffer.replace(i, i+1, AMPERSAND_ENTITY); 399 size += 4; 400 i += 4; 401 break; 402 } 403 } 404 return buffer.toString(); 405 } 406 407 /*** 408 * <p>Escape the <code>toString</code> of the given object. 409 * For use in an attribute value.</p> 410 */ 411 protected String escapeAttributeValue(Object value) { 412 StringBuffer buffer = new StringBuffer(value.toString()); 413 for (int i=0, size = buffer.length(); i <size; i++) { 414 switch (buffer.charAt(i)) { 415 case '<': 416 buffer.replace(i, i+1, LESS_THAN_ENTITY); 417 size += 3; 418 i+=3; 419 break; 420 case '>': 421 buffer.replace(i, i+1, GREATER_THAN_ENTITY); 422 size += 3; 423 i += 3; 424 break; 425 case '&': 426 buffer.replace(i, i+1, AMPERSAND_ENTITY); 427 size += 4; 428 i += 4; 429 break; 430 case '\'': 431 buffer.replace(i, i+1, APOSTROPHE_ENTITY); 432 size += 4; 433 i += 4; 434 break; 435 case '\"': 436 buffer.replace(i, i+1, QUOTE_ENTITY); 437 size += 5; 438 i += 5; 439 break; 440 } 441 } 442 return buffer.toString(); 443 } 444 445 }

This page was automatically generated by Maven